package com.lry.rpc.starter.extend;

import com.lry.rpc.starter.util.AnnotationUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.annotation.InjectionMetadata;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

@Slf4j
public abstract class AbstractAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor , MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware, BeanClassLoaderAware, EnvironmentAware, DisposableBean {

    private static final int CACHE_SIZE = Integer.getInteger("", 32);

    private final Class<? extends Annotation>[] annotationTypes;

    private ClassLoader classLoader;

    private Environment environment;

    private ConfigurableListableBeanFactory beanFactory;

    private final ConcurrentMap<String, AbstractAnnotationBeanPostProcessor.AnnotatedInjectionMetadata> injectionMetadataCache;

    private final ConcurrentMap<String, Object> injectedObjectsCache;

    public AbstractAnnotationBeanPostProcessor(Class... annotationTypes) {
        this.injectionMetadataCache = new ConcurrentHashMap(CACHE_SIZE);
        this.injectedObjectsCache = new ConcurrentHashMap(CACHE_SIZE);
        Assert.notEmpty(annotationTypes, "The argument of annotations' types must not empty");
        this.annotationTypes = annotationTypes;
    }

    @Override
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeanCreationException {
        InjectionMetadata metadata = this.findInjectionMetadata(beanName, bean.getClass(), pvs);

        try {
            metadata.inject(bean, beanName, pvs);
            return pvs;
        } catch (BeanCreationException e) {
            throw e;
        } catch (Throwable throwable) {
            throw new BeanCreationException(beanName, "Injection of @" + this.getAnnotationTypes()[0].getSimpleName() + " dependencies is failed", throwable);
        }
    }

    private InjectionMetadata findInjectionMetadata(String beanName, Class<?> clazz, PropertyValues pvs) {
        String cacheKey = StringUtils.hasLength(beanName) ? beanName : clazz.getName();
        AbstractAnnotationBeanPostProcessor.AnnotatedInjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
        if (!InjectionMetadata.needsRefresh(metadata, clazz)) {
            return metadata;
        }
        synchronized (this.injectionMetadataCache) {
            metadata = this.injectionMetadataCache.get(cacheKey);
            if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                if (metadata != null) {
                    metadata.clear(pvs);
                }
                try {
                    metadata = this.buildAnnotatedMetadata(clazz);
                    this.injectionMetadataCache.put(cacheKey, metadata);
                } catch (NoClassDefFoundError var9) {
                    throw new IllegalStateException("Failed to introspect object class [" + clazz.getName() + "] for annotation metadata: could not find class that it depends on", var9);
                }
            }
        }

        return metadata;
    }

    private AbstractAnnotationBeanPostProcessor.AnnotatedInjectionMetadata buildAnnotatedMetadata(Class<?> beanClass) {
        Collection<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement> fieldElements = this.findFieldAnnotationMetadata(beanClass);
        Collection<AbstractAnnotationBeanPostProcessor.AnnotatedMethodElement> methodElements = this.findAnnotatedMethodMetadata(beanClass);
        return new AbstractAnnotationBeanPostProcessor.AnnotatedInjectionMetadata(beanClass, fieldElements, methodElements);
    }

    private List<AbstractAnnotationBeanPostProcessor.AnnotatedMethodElement> findAnnotatedMethodMetadata(Class<?> beanClass) {
        final List<AbstractAnnotationBeanPostProcessor.AnnotatedMethodElement> elements = new LinkedList();
        ReflectionUtils.doWithMethods(beanClass,method->{
            Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
            if (BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                for (Class<? extends Annotation> annotationType : this.getAnnotationTypes()) {
                    AnnotationAttributes attributes = AnnotationUtils.getAnnotationAttributes(bridgedMethod, annotationType, AbstractAnnotationBeanPostProcessor.this.getEnvironment(), false, true, new String[0]);
                    if (attributes != null && method.equals(ClassUtils.getMostSpecificMethod(method, beanClass))) {
                        if (Modifier.isStatic(method.getModifiers())) {
                            if (log.isWarnEnabled()) {
                                log.warn("@" + annotationType.getName() + " annotation is not supported on static methods: " + method);
                            }
                            return;
                        }

                        PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, beanClass);
                        elements.add(new AnnotatedMethodElement(method, pd, attributes));

                    }
                }
            }
        });
        return elements;
    }

    private List<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement> findFieldAnnotationMetadata(Class<?> beanClass) {
        final List<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement> elements = new LinkedList();
        ReflectionUtils.doWithFields(beanClass,field->{
            for (Class<? extends Annotation> annotationType : this.getAnnotationTypes()) {
                AnnotationAttributes attributes = AnnotationUtils.getAnnotationAttributes(field, annotationType, AbstractAnnotationBeanPostProcessor.this.getEnvironment(), false, true, new String[0]);
                if(null!=attributes){
                    if (Modifier.isStatic(field.getModifiers())) {
                        if (log.isWarnEnabled()) {
                            log.warn("@" + annotationType.getName() + " is not supported on static fields: " + field);
                        }
                        return;
                    }
                    elements.add(new AnnotatedFieldElement(field, attributes));
                }
            }
        });
        return elements;
    }




    protected final Class<? extends Annotation>[] getAnnotationTypes() {
        return this.annotationTypes;
    }

    private static <T> Collection<T> combine(Collection... elements) {
        List<T> allElements = new ArrayList();
        for (Collection element : elements) {
            allElements.addAll(element);
        }
        return allElements;
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory, "AnnotationInjectedBeanPostProcessor requires a ConfigurableListableBeanFactory");
        this.beanFactory = (ConfigurableListableBeanFactory)beanFactory;
    }

    @Override
    public void destroy() throws Exception {
        for (Object value : this.injectedObjectsCache.values()) {
            if (log.isInfoEnabled()) {
                log.info(value + " was destroying!");
            }
            if (value instanceof DisposableBean) {
                ((DisposableBean)value).destroy();
            }
        }

        this.injectionMetadataCache.clear();
        this.injectedObjectsCache.clear();
        if (log.isInfoEnabled()) {
            log.info(this.getClass() + " was destroying!");
        }

    }

    @Override
    public void postProcessMergedBeanDefinition(RootBeanDefinition rootBeanDefinition, Class<?> beanType, String beanName) {
        if (beanType != null) {
            InjectionMetadata metadata = this.findInjectionMetadata(beanName, beanType, (PropertyValues)null);
            metadata.checkConfigMembers(rootBeanDefinition);
        }

    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public int getOrder() {
        return 2147483644;
    }



    protected Environment getEnvironment() {
        return this.environment;
    }

    protected ClassLoader getClassLoader() {
        return this.classLoader;
    }

    protected ConfigurableListableBeanFactory getBeanFactory() {
        return this.beanFactory;
    }

    protected Collection<Object> getInjectedObjects() {
        return this.injectedObjectsCache.values();
    }

    protected Object getInjectedObject(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType, InjectionMetadata.InjectedElement injectedElement) throws Exception {
        String cacheKey = this.buildInjectedObjectCacheKey(attributes, bean, beanName, injectedType, injectedElement);
        Object injectedObject = this.injectedObjectsCache.get(cacheKey);
        if (injectedObject == null) {
            injectedObject = this.doGetInjectedBean(attributes, bean, beanName, injectedType, injectedElement);
            this.injectedObjectsCache.putIfAbsent(cacheKey, injectedObject);
        }

        return injectedObject;
    }

    protected abstract Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType, InjectionMetadata.InjectedElement injectedElement) throws Exception;

    protected abstract String buildInjectedObjectCacheKey(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType, InjectionMetadata.InjectedElement injectedElement);

    protected Map<InjectionMetadata.InjectedElement, Object> getInjectedFieldObjectsMap() {

        Map<InjectionMetadata.InjectedElement, Object> injectedElementBeanMap = new LinkedHashMap();

        for (AnnotatedInjectionMetadata metadata : this.injectionMetadataCache.values()) {
            Collection<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement> fieldElements = metadata.getFieldElements();
            for (AnnotatedFieldElement fieldElement : fieldElements) {
                injectedElementBeanMap.put(fieldElement, fieldElement.bean);
            }
        }

        return Collections.unmodifiableMap(injectedElementBeanMap);
    }

    protected Map<InjectionMetadata.InjectedElement, Object> getInjectedMethodObjectsMap() {
        Map<InjectionMetadata.InjectedElement, Object> injectedElementBeanMap = new LinkedHashMap();

        for (AnnotatedInjectionMetadata metadata : this.injectionMetadataCache.values()) {
            Collection<AbstractAnnotationBeanPostProcessor.AnnotatedMethodElement> methodElements = metadata.getMethodElements();
            for (AnnotatedMethodElement methodElement : methodElements) {
                injectedElementBeanMap.put(methodElement, methodElement.object);
            }
        }
        return Collections.unmodifiableMap(injectedElementBeanMap);
    }

    public class AnnotatedFieldElement extends InjectionMetadata.InjectedElement {
        private final Field field;
        private final AnnotationAttributes attributes;
        private volatile Object bean;

        protected AnnotatedFieldElement(Field field, AnnotationAttributes attributes) {
            super(field, null);
            this.field = field;
            this.attributes = attributes;
        }

        @Override
        protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
            Class<?> injectedType = this.resolveInjectedType(bean, this.field);
            Object injectedObject = AbstractAnnotationBeanPostProcessor.this.getInjectedObject(this.attributes, bean, beanName, injectedType, this);
            ReflectionUtils.makeAccessible(this.field);
            this.field.set(bean, injectedObject);
        }

        private Class<?> resolveInjectedType(Object bean, Field field) {
            Type genericType = field.getGenericType();
            return genericType instanceof Class ? field.getType() : GenericTypeResolver.resolveTypeArgument(AopUtils.getTargetClass(bean), field.getDeclaringClass());
        }
    }

    private class AnnotatedMethodElement extends InjectionMetadata.InjectedElement {
        private final Method method;
        private final AnnotationAttributes attributes;
        private volatile Object object;

        protected AnnotatedMethodElement(Method method, PropertyDescriptor pd, AnnotationAttributes attributes) {
            super(method, pd);
            this.method = method;
            this.attributes = attributes;
        }

        protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
            Class<?> injectedType = this.pd.getPropertyType();
            Object injectedObject = AbstractAnnotationBeanPostProcessor.this.getInjectedObject(this.attributes, bean, beanName, injectedType, this);
            ReflectionUtils.makeAccessible(this.method);
            this.method.invoke(bean, injectedObject);
        }
    }

    private class AnnotatedInjectionMetadata extends InjectionMetadata {
        private final Collection<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement> fieldElements;
        private final Collection<AbstractAnnotationBeanPostProcessor.AnnotatedMethodElement> methodElements;

        public AnnotatedInjectionMetadata(Class<?> targetClass, Collection<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement> fieldElements, Collection<AbstractAnnotationBeanPostProcessor.AnnotatedMethodElement> methodElements) {
            super(targetClass, AbstractAnnotationBeanPostProcessor.combine(fieldElements, methodElements));
            this.fieldElements = fieldElements;
            this.methodElements = methodElements;
        }

        public Collection<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement> getFieldElements() {
            return this.fieldElements;
        }

        public Collection<AbstractAnnotationBeanPostProcessor.AnnotatedMethodElement> getMethodElements() {
            return this.methodElements;
        }
    }
}
